概述
本篇将对Android的类加载机制进行分析。总体来说Android的ClassLoader分为系统ClassLoader和自定义的ClassLoader
系统的包括有三种:
- BootClassLoader Android系统启动时会使用BootClassLoader预加载一些类。它位于类加载器链的头部。
- PathClassLoader 可以加载已经安装的apk,即也就是/data/app/package 下的apk文件,也可以加载/vendor/lib, /system/lib下的nativeLibrary。
- DexClassLoader,可以加载一个未安装的apk文件。
我们在App中使用的系统类加载器默认是PathClassLoader,它的父加载器是BootClassLoader。为什么是PathClassLoader呢?因为在contextImpl的getClassLoader有如下实现
1 |
|
从上面可以看出ContextImpl为用户提供了PathClassLoader来供App加载。
BaseDexClassLoader
Android中使用PathClassLoader来加载类,其实实现的就是从本地文件系统中加载类,它继承自BaseDexClassLoader,BaseDexClassLoader继承子ClassLoader,ClassLoader是一个抽象类。
1 | // libcore/libdvm/src/main/java/java/lang/ClassLoader.java |
可以看出ClassLoader为我们实现了类加载的基本逻辑,首先它通过findLoadedClass查找要加载的类是否已经加载,如果已经加载了就直接返回,否则通过parent loader进行加载,这符合双亲委派的加载模型,如果父类loader找到该类并加载则返回,否则通过子类加载器进行加载,子类加载时通过findClass进行加载的。所以需要实现findClass的具体逻辑。那么在BaseDexClassLoader我们需要重点关注findClass的类加载逻辑。
1 | //libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java |
我们发现BaseDexClassLoader的实现非常简单,它内部有一个DexPathList成员pathList,在构造方法中进行初始化,它代表了一个jar/apk文件列表,在这些文件之中包含了class文件和资源文件。而在findClass中实际上是将加载类的任务委托给了pathList。那么就需要再取分析DexPathList了,在这之前我们看看它大概会包含的信息,这是我在应用中打印的ClassLoader信息,它内部的DexPathList包含了该apk和lib的信息。
classLoader: dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/com.yujian.myapplication-2.apk”],nativeLibraryDirectories=[/data/app-lib/com.yujian.myapplication-2, /system/lib]]]
1 | // libcore/dalvik/src/main/java/dalvik/system/DexPathList.java |
DexPathList有两个成员dexElements和nativeLibraryDirectories,分别用来描述dex/apk文件信息和lib文件。它们都是在DexPathList构造方法中进行初始化的。其中dexElements是通过makeDexElements和splitLibraryPath生成的。其中Element的定义如下
1 | static class Element { |
1 | private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory, |
makeDexElements通过dexPath代表的dex/apk文件或者目录生成对应的elements数组,在构造DexPathList时传递的dexPath时可能包含多个文件路径的,我们上面打印的信息就只有一个apk,这里需要注意,这些文件路径经过splitDexPath返回一个ArrayList
- 如果时dex文件,通过loadDexFile加载,并返回一个描述该文件的DexFile,
- 如果时zip/apk/jar,先保存压缩文件到zip中,然后通过loadDexFile加载并返回要描述的dex文件的DexFile
- 如果时目录,直接为其生成一个Element并添加到elements数组中
对于1和2两种情况最终也会未其分别创建Element并添加到elements数组中,这个elements数组最终就是我们要的dexElements。
1 | private static DexFile loadDexFile(File file, File optimizedDirectory) |
loadDexFile实际上只是为file创建一个DexFile对象。从名称上看它是专门处理dex文件的。
下面我们就看看DexPathList是如何加载类的
1 |
|
DexPathList先遍历dexElements,对于DexFile通过loadClassBinaryName来加载,如果找到就返回。这里又转到DexFile进行加载了,所以需要再看看DexFile是如何加载的。
1 | public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) { |
DexFile调用defainClassNative方法来加载类,这里从名称看,它实际上是从native层加载类的,实现在runtime/native/dalvik_system_DexFile.cc。关于native层如何加载类我们在另外的篇章中进行分析。这样DexPathList加载类的逻辑就分析完成了。
需要注意的是DexPathList遍历dexElements通过DexFile来进行的类加载的方式,为一些基于muti dex的热修复技术提供了可能,因为在dexElements数组中靠前的dex文件首先被访问到,这样被修复的类可以被优先加载。
PathClassLoader
PathClassLoader是BaseDexClassLoader的子类,它的类加载功能正是依赖于其父类。
我们看看它的实现
1 | public class PathClassLoader extends BaseDexClassLoader { |
PathClassLoader的实现非常简单,只是提供了两个不同的构造方法,这两个构造方法的区别在于是否提供了lib path,默认情况下的path就是system/lib/和data/app-lib/pakage-name
DexClassLoader
1 | /** |
DexClassLoader的实现更加简单,只有一个构造方法,从注释也可以看出它可以从包含classes.dex文件的jar/apk文件中来加载类,而不需要jar/apk为已安装应用的一部分。因为它提供了一个optimizedDirectory参数,这个参数是一个应用私有且可写入的目录,用来保存dex经过优化后的类,需要注意的是为了防止注入,优化后的类是不能被保存在外置存储上的。在PathClassLoader中我们看到这个参数默认是null,也就是它会从默认的位置加载dex,这个位置就是/data/dalvik-cache,也就是已经安装的apk。这也是它们之间最大的区别了。